package com.anonymous.server;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map.Entry;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;

import com.anonymous.message.Message;
import com.anonymous.message.PostUserListMsgArrayList;
import com.anonymous.model.User;
import com.anonymous.model.UserList;

/* Author: Sungkyu Kang
 * Description
 *     This class maintains list of login users in the system while server is running.
 *   This thread class contains accessing data in itself and designed to be thread safe
 *   from calling by other objects.
 */
public class HashtableUserList implements UserList<Hashtable<String, User>>,
		Runnable {

	private static final long serialVersionUID = 1L;
	Hashtable<String, User> users;

	private boolean change = false;
	private int port = 55678;
	private KeyStore serverKeyStore;
	private KeyStore clientKeyStore;
	private SSLContext sslContext;
	private String passphrase;
	// Choose another data structure.
	// Chosen for convenience, the uniqueness of username, and for small number
	// of users.

	// Single thread allowed in one system
	public HashtableUserList(KeyStore keyStore, String passphrase) {

		//the clientKeyStore keeps the KeyStore of server
		clientKeyStore = keyStore;
		this.passphrase = passphrase;
		users = new Hashtable<String, User>();
	}

	public boolean removeUser(User user) {
		synchronized (users) {
			users.remove(user.getUserName());
			change = true;
			users.notifyAll();
		}
		return false;
	}

	@Override
	public void addUser(User user) {
		synchronized (users) {
			users.put(user.getUserName(), user);
			change = true;
			users.notifyAll();
		}
	}

	@Override
	public void run() {
		while (true) {
			synchronized (users) {
				while (!change)

					try {
						users.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				// message send start
				ObjectOutputStream oos;

				/*
				 * ByteArrayOutputStream bArrayOutputStream = new
				 * ByteArrayOutputStream(); byte[] toSend = null; try { oos =
				 * new ObjectOutputStream(bArrayOutputStream);
				 * oos.writeObject(new PostUserListMsgHashtable(users)); toSend
				 * = bArrayOutputStream.toByteArray(); } catch (IOException e) {
				 * e.printStackTrace(); }
				 */

				PostUserListMsgArrayList msg = new PostUserListMsgArrayList();
				msg.setUserList(new ArrayList<User>(users.values()));
				Iterator<Entry<String, User>> iterator = users.entrySet()
						.iterator();

				users.values().toArray();
				User currentUser;
				while (iterator.hasNext()) {

					currentUser = iterator.next().getValue();
					System.out.println("Userlist update called for "
							+ currentUser.getUserName());

					try {
						setupServerKeystore(currentUser.getCertificate());
						//the ClientKeyStore keeps the KeyStore of server
						setupSSLContext();
						SSLSocketFactory sf = sslContext.getSocketFactory();
						//In sending userlist, server is the client trying to connect to client.
						SSLSocket clientSocket = (SSLSocket) sf
								.createSocket(currentUser.getIpAddress(), port);
	

						oos = new ObjectOutputStream(clientSocket.getOutputStream());
						oos.writeObject((Message) msg);
					} catch (Exception e) {
						e.printStackTrace();
						continue;
					}
				}
				// message send end
				change = false;
			}
		}
	}

	private void setupSSLContext() throws Exception {
		// TODO Auto-generated method stub
		TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
		tmf.init(serverKeyStore);

		KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
		kmf.init(clientKeyStore, passphrase.toCharArray());
		SecureRandom secureRandom = new SecureRandom();
		secureRandom.nextInt();
		sslContext = SSLContext.getInstance("TLS");
		sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(),
				secureRandom);
		
	}

	private void setupServerKeystore(X509Certificate clientCertificate) throws Exception{
		// TODO Auto-generated method stub
		serverKeyStore = KeyStore.getInstance("JKS");
		//GENERATE AN EMPTY clientKeyStore
		serverKeyStore.load(null,
				passphrase.toCharArray());
        
	    // Add the certificate
		serverKeyStore.setCertificateEntry("clientCertificate", clientCertificate);
	}

	@SuppressWarnings("unchecked")
	@Override
	public Hashtable<String, User> getUsers() {
		return (Hashtable<String, User>) users.clone();
	}

	public Enumeration<User> getUserIterator() {
		Enumeration<User> s = users.elements();
		return s;
	}

	@Override
	public boolean isUser(String username) {
		return users.containsKey(username);
	}

}
